﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Globalization;


// AS OF 04/10/2011:
// - THE LATEST 4.1.0 BUILD (UNRELEASED PATCH) IS 13726
// - THE LATEST LIVE BUILD IS 13623 according to wow client


namespace Rawr.TalentClassGenerator
{
	public partial class FormTalentClassGeneratorWowTal : Form
	{
		public FormTalentClassGeneratorWowTal()
		{
			InitializeComponent();
		}

		private void buttonGenerateCode_Click(object sender, EventArgs e)
		{
            textBoxCode.Text = @"using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Rawr
{
	/// <summary>
	/// This file is automatically generated by the Rawr.TalentClassGenerator tool. Please don't edit it directly.
	/// </summary>
	[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
	public sealed class TalentDataAttribute : Attribute
	{
		public TalentDataAttribute(int index, string name, int maxPoints, int tree, int column, int row, int prerequisite, string[] description, string icon)
		{
			_index = index;
			_name = name;
			_maxPoints = maxPoints;
			_tree = tree;
			_column = column;
			_row = row;
			_prerequisite = prerequisite;
			_description = description;
			_icon = icon;
		}

		private readonly int _index;
		private readonly string _name;
		private readonly int _maxPoints;
		private readonly int _tree;
		private readonly int _column;
		private readonly int _row;
		private readonly int _prerequisite;
		private readonly string _icon;
		private readonly string[] _description;

		public int Index { get { return _index; } }
		public string Name { get { return _name; } }
		public int MaxPoints { get { return _maxPoints; } }
		public int Tree { get { return _tree; } }
		public int Column { get { return _column; } }
		public int Row { get { return _row; } }
		public int Prerequisite { get { return _prerequisite; } }
		public string[] Description { get { return _description; } }
		public string Icon { get { return _icon; } }
	}

	public abstract class TalentsBase
	{
		public abstract int[] Data { get; }
		public virtual bool[] GlyphData { get { return null; } }
        //
        public virtual int TreeStartingIndexes_0 { get { return 0; } }
        public virtual int TreeStartingIndexes_1 { get { return 0; } }
        public virtual int TreeStartingIndexes_2 { get { return 0; } }
        public int[] TreeStartingIndexes { get { return new int[] { TreeStartingIndexes_0, TreeStartingIndexes_1, TreeStartingIndexes_2 }; } }
        public int[] TreeLengths {
            get {
                return new int[] {
                    TreeStartingIndexes_1,
                    TreeStartingIndexes_2 - TreeStartingIndexes_1,
                    Data.Length - TreeStartingIndexes_2,
                };
            }
        }
        public virtual int GlyphTreeStartingIndexes_0 { get { return 0; } }
        public virtual int GlyphTreeStartingIndexes_1 { get { return 0; } }
        public virtual int GlyphTreeStartingIndexes_2 { get { return 0; } }
        public int[] GlyphTreeStartingIndexes { get { return new int[] { GlyphTreeStartingIndexes_0, GlyphTreeStartingIndexes_1, GlyphTreeStartingIndexes_2 }; } }
        public int[] GlyphTreeLengths {
            get {
                return new int[] {
                    GlyphTreeStartingIndexes_1,
                    GlyphTreeStartingIndexes_2 - GlyphTreeStartingIndexes_1,
                    GlyphData.Length - GlyphTreeStartingIndexes_2,
                };
            }
        }
        public int[] treeCounts = { -1, -1, -1 };
        public int[] glyphtreeCounts = { -1, -1, -1 };
        //
        public int[] TreeCounts
        {
            get
            {
                if (treeCounts[0] == -1)
                {
                    treeCounts[0] = 0;
                    for (int i = TreeStartingIndexes[0]; i < TreeStartingIndexes[1]; i++)
                    {
                        treeCounts[0] += Data[i];
                    }
                }
                if (treeCounts[1] == -1)
                {
                    treeCounts[1] = 0;
                    for (int i = TreeStartingIndexes[1]; i < TreeStartingIndexes[2]; i++)
                    {
                        treeCounts[1] += Data[i];
                    }
                }
                if (treeCounts[2] == -1)
                {
                    treeCounts[2] = 0;
                    for (int i = TreeStartingIndexes[2]; i < Data.Length; i++)
                    {
                        treeCounts[2] += Data[i];
                    }
                }
                return treeCounts;
            }
            protected set { treeCounts = value; }
        }
        public int[] GlyphTreeCounts
        {
            get
            {
                if (glyphtreeCounts[0] == -1)
                {
                    glyphtreeCounts[0] = 0;
                    for (int i = GlyphTreeStartingIndexes[0]; i < GlyphTreeStartingIndexes[1]; i++)
                    {
                        glyphtreeCounts[0] += GlyphData[i] ? 1 : 0;
                    }
                }
                if (glyphtreeCounts[1] == -1)
                {
                    glyphtreeCounts[1] = 0;
                    for (int i = GlyphTreeStartingIndexes[1]; i < GlyphTreeStartingIndexes[2]; i++)
                    {
                        glyphtreeCounts[1] += GlyphData[i] ? 1 : 0;
                    }
                }
                if (glyphtreeCounts[2] == -1)
                {
                    glyphtreeCounts[2] = 0;
                    for (int i = GlyphTreeStartingIndexes[2]; i < GlyphData.Length; i++)
                    {
                        glyphtreeCounts[2] += GlyphData[i] ? 1 : 0;
                    }
                }
                return glyphtreeCounts;
            }
            protected set { glyphtreeCounts = value; }
        }
        //
        public int HighestTree
        {
            get
            {
                int[] trees = TreeCounts;
                if (trees[0] >= trees[1] && trees[0] >= trees[2]) { return 0; }
                if (trees[1] >= trees[0] && trees[1] >= trees[2]) { return 1; }
                if (trees[2] >= trees[0] && trees[2] >= trees[1]) { return 2; }
                return 0;
            }
        }
        //
        protected void LoadString(string code, int[][] glyphEncodes=null)
        {
            if (string.IsNullOrEmpty(code)) return;
            int[] _data = Data;
            string[] tmp = code.Split('.');
            string talents = tmp[0];
            if (talents.Length >= _data.Length)
            {
                List<int> data = new List<int>();
                foreach (Char digit in talents)
                    data.Add(int.Parse(digit.ToString()));
                data.CopyTo(0, _data, 0, _data.Length);
            }
            if (tmp.Length > 1)
            {
                string glyphs = tmp[1];
                bool[] _glyphData = GlyphData;
                if (_glyphData != null && glyphs.Length == _glyphData.Length)
                {
                    List<bool> data = new List<bool>();
                    foreach (Char digit in glyphs)
                        data.Add(int.Parse(digit.ToString()) == 1);
                    data.CopyTo(_glyphData);
                }
            } else if (glyphEncodes != null) {
                // This makes it determine the glyphs active from WowHead Glyph IDs
                // (numbered as [0-2][0-5]), see TalentPicker.xaml.cs for details
                bool[] _glyphData = GlyphData;
                if (_glyphData != null) {
                    List<GlyphDataAttribute> glyphsInOrder_Prime = new List<GlyphDataAttribute>();
                    List<GlyphDataAttribute> glyphsInOrder_Major = new List<GlyphDataAttribute>();
                    List<GlyphDataAttribute> glyphsInOrder_Minor = new List<GlyphDataAttribute>();
                    foreach (PropertyInfo pi in this.GetType().GetProperties()) {
                        GlyphDataAttribute[] glyphDatas = pi.GetCustomAttributes(typeof(GlyphDataAttribute), true) as GlyphDataAttribute[];
                        if (glyphDatas.Length > 0) {
                            if      (glyphDatas[0].Type == GlyphType.Prime) { glyphsInOrder_Prime.Add(glyphDatas[0]); }
                            else if (glyphDatas[0].Type == GlyphType.Major) { glyphsInOrder_Major.Add(glyphDatas[0]); }
                            else if (glyphDatas[0].Type == GlyphType.Minor) { glyphsInOrder_Minor.Add(glyphDatas[0]); }
                        }
                    }
                    glyphsInOrder_Prime.Sort(CompareGlyphsById);
                    glyphsInOrder_Major.Sort(CompareGlyphsById);
                    glyphsInOrder_Minor.Sort(CompareGlyphsById);
                    int counter = 0;
                    for (int i = 0; i < glyphsInOrder_Prime.Count; i++)
                    {
                        for (int e = 0; e < 3; e++)
                        {
                            if (counter == glyphEncodes[0][e]) { _glyphData[glyphsInOrder_Prime[i].Index] = true; break; }
                        }
                        // Iterate Counter, but we can't do 6-9 of any 10 block
                        counter++;
                        if (counter == 6 || counter == 16 || counter == 26) { counter -= 6; counter += 10; }
                    }
                }
            }
        }

        private static int CompareGlyphsById(GlyphDataAttribute x, GlyphDataAttribute y) {
            if (x == null) {
                if (y == null) {
                    // If x is null and y is null, they're equal
                    return 0;
                } else {
                    // If x is null and y is not null, y is greater
                    return -1;
                }
            } else {
                // If x is not null...
                if (y == null) { // ...and y is null, x is greater.
                    return 1;
                } else {
                    // sort them with ordinary string comparison.
                    return x.SpellID.CompareTo(y.SpellID);
                }
            }
        }
        //
		public override string ToString()
		{
			StringBuilder ret = new StringBuilder();
			foreach (int digit in Data)
				ret.Append(digit.ToString());
			if (GlyphData != null)
			{
				ret.Append('.');
				foreach (bool glyph in GlyphData)
				{
					ret.Append(glyph ? '1' : '0');
				}
			}
			return ret.ToString();
		}

		public CharacterClass GetClass()
		{
			if (GetType() == typeof(WarlockTalents)) return CharacterClass.Warlock;
			if (GetType() == typeof(MageTalents)) return CharacterClass.Mage;
			if (GetType() == typeof(PriestTalents)) return CharacterClass.Priest;
			if (GetType() == typeof(DruidTalents)) return CharacterClass.Druid;
			if (GetType() == typeof(RogueTalents)) return CharacterClass.Rogue;
			if (GetType() == typeof(HunterTalents)) return CharacterClass.Hunter;
			if (GetType() == typeof(ShamanTalents)) return CharacterClass.Shaman;
			if (GetType() == typeof(DeathKnightTalents)) return CharacterClass.DeathKnight;
			if (GetType() == typeof(PaladinTalents)) return CharacterClass.Paladin;
			return CharacterClass.Warrior;
		}

		public abstract TalentsBase Clone();
	}

";
			List<ClassData> classes = new List<ClassData>();
			classes.Add(new ClassData()
			{
				ID = 1,
				Name = "Warrior",
				TreeNames = new string[] { "Arms", "Fury", "Protection" },
				TreeIDs = new string[] { "746", "815", "845" }
			});
			classes.Add(new ClassData()
			{
				ID = 2,
				Name = "Paladin",
				TreeNames = new string[] { "Holy", "Protection", "Retribution" },
				TreeIDs = new string[] { "831", "839", "855" }
			});
			classes.Add(new ClassData()
			{
				ID = 3,
				Name = "Hunter",
				TreeNames = new string[] { "Beast Mastery", "Marksmanship", "Survival" },
				TreeIDs = new string[] { "811", "807", "809" }
			});
			classes.Add(new ClassData()
			{
				ID = 4,
				Name = "Rogue",
				TreeNames = new string[] { "Assassination", "Combat", "Subtlety" },
				TreeIDs = new string[] { "182", "181", "183" }
			});
			classes.Add(new ClassData()
			{
				ID = 5,
				Name = "Priest",
				TreeNames = new string[] { "Discipline", "Holy", "Shadow" },
				TreeIDs = new string[] { "760", "813", "795" }
			});
			classes.Add(new ClassData()
			{
				ID = 6,
				Name = "DeathKnight",
				TreeNames = new string[] { "Blood", "Frost", "Unholy" },
				TreeIDs = new string[] { "398", "399", "400" }
			});
			classes.Add(new ClassData()
			{
				ID = 7,
				Name = "Shaman",
				TreeNames = new string[] { "Elemental", "Enhancement", "Restoration" },
				TreeIDs = new string[] { "261", "263", "262" }
			});
			classes.Add(new ClassData()
			{
				ID = 8,
				Name = "Mage",
				TreeNames = new string[] { "Arcane", "Fire", "Frost" },
				TreeIDs = new string[] { "799", "851", "823" }
			});
			classes.Add(new ClassData()
			{
				ID = 9,
				Name = "Warlock",
				TreeNames = new string[] { "Affliction", "Demonology", "Destruction" },
				TreeIDs = new string[] { "871", "867", "865" }
			});
			classes.Add(new ClassData()
			{
				ID = 11,
				Name = "Druid",
				TreeNames = new string[] { "Balance", "Feral Combat", "Restoration" },
				TreeIDs = new string[] { "752", "750", "748" }
			});



			//for (int i = 0; i < classId.Length; i++)
			foreach (ClassData classData in classes)
			{
                string read = "";
                try {
                    ProcessTalentDataJSON(read = new StreamReader(System.Net.HttpWebRequest.Create(
                        string.Format("http://static.mmo-champion.com/db/js/talents/{0}-{1}.js",
                        textBoxUrl.Text, classData.Name.ToLower())).GetResponse().GetResponseStream()).ReadToEnd(), classData);
                } catch (Exception ex) {
                    textBoxCode.Text = "FAILED on: " + classData.Name + " : " + ex.Message + "\r\n\r\n" + ex.StackTrace + "\r\n\r\n" + read;
                    textBoxUrl.Text = (int.Parse(textBoxUrl.Text) - 1).ToString();
                    return;
                }
			}
            textBoxCode.Text += "}\r\n";
			textBoxCode.SelectAll();
			textBoxCode.Focus();
		}

		private void ProcessTalentDataJSON(string fullResponse, ClassData classData)
		{
			//Clean formatting out of the Descriptions
			fullResponse = fullResponse
				.Replace("</div></div>", "\r\n")
				.Replace("\\r\\n", "\r\n")
				.Replace(" <NNF>", "")
				.Replace(" <NYI>", "")
				.Replace("&#39;", "'")
				.Replace("</div>", " - ")
				.Replace("<span style=\"color:#FFFFFF;\">", "")
                .Replace("<span style=\"color: #FFFFFF;\">", "")
				.Replace("</span>", "")
				.Replace("<div class=\"tt-spell sigrie-tooltip\">", "")
				.Replace("<div class=\"tt-name tts-name\">", "")
				.Replace("<div class=\"tts-rank\">", "")
				.Replace("</a>", "")
				.Replace("<div class=\"tt-spell-range_power\">", "")
				.Replace("<div class=\"tts-power_cost\">", "")
				.Replace("<div class=\"tt-spell-cast_cooldown\">", "")
				.Replace("<div class=\"tts-cooldown tt-spell-right\">", "")
				.Replace("<div class=\"tts-cast_time\">", "")
				.Replace("<div class=\"tts-description\">", "")
				.Replace("<div class=\"tts-range\">", "")
				.Replace("<div class=\"tts-required_tools\">", "")
				.Replace("<div class=\"tts-rune_cost tts-power_cost\">", "")
				.Replace("<div class=\"tts-required_stances\">", "")
				.Replace("<div class=\"tts-range tt-spell-right\">", "")
				.Replace("<div class=\"tts-power_cost tts-power_percent\">", "")
				.Replace("<div class=\"tts-required_item_subclasses\">", "");
			while (fullResponse.Contains("<a href=\""))
			{
				string href = fullResponse.Between("<a href=\"", "\">");
				fullResponse = fullResponse.Replace("<a href=\"" + href + "\">", "");
			}


			List<TalentData> talents = new List<TalentData>();
			string[] allTalents = fullResponse.Between("[\"talents\"] = {", "}};").Split(new string[] { "}," }, StringSplitOptions.None);
			string[] allDescriptions = fullResponse.Between("[\"tooltips\"] = {", "}};").Split(new string[] { "}," }, StringSplitOptions.None);

			foreach (string strTalent in allTalents)
			{
				//10496: {'depends_count': 0, 'depends': 10486, 'max_ranks': 3, 'name': 'Sword and Board', 'tab': 845, 'column': 2, 'icon': 'ability_warrior_swordandboard', 'active': false, 'row': 5

				TalentData talent = new TalentData();
				talent.ID = strTalent.Before(":").Trim();
				talent.Name = strTalent.Between("'name': ", ", 'tab'").Trim('\'','"');
				talent.Icon = strTalent.Between("'icon': '", "',"); ;
				talent.MaxPoints = int.Parse(strTalent.Between("'max_ranks': ", ","));
				talent.Prerequisite = int.Parse(strTalent.Between("'depends': ", ",")); ; //Set this to the ID for now, will update it to be the Index in a moment
				if (string.IsNullOrWhiteSpace(talent.Name) || talent.MaxPoints == 0) continue;
				string treeID = strTalent.Between("'tab': ", ",");
				talent.Tree = treeID == classData.TreeIDs[0] ? 0 : (treeID == classData.TreeIDs[1] ? 1 : 2);
				talent.Row = int.Parse(strTalent.After("'row': ")) + 1;
				talent.Column = int.Parse(strTalent.Between("'column': ", ",")) + 1;
				string strDescription = allDescriptions.First(str => str.Trim().StartsWith(talent.ID + ":")).After("{");
				if (strDescription.After(": ")[0]=='\'')
				{ //It's quoted using '
					talent.Description = strDescription.TrimEnd('\'')
						.Split(new string[] { "'," }, StringSplitOptions.None).Select(str => str.After("'").Trim()).ToArray();
				}
				else
				{ //It's quoted using "
					talent.Description = strDescription.TrimEnd('"')
						.Split(new string[] { "\"," }, StringSplitOptions.None).Select(str => str.After("\"").Trim()).ToArray();
				}
				// 1: "Your critical strikes cause the opponent to bleed, dealing 16% of your melee weapon's average damage over 6 sec.", 2: "Your critical strikes cause the opponent to bleed, dealing 32% of your melee weapon's average damage over 6 sec.", 3: "Your critical strikes cause the opponent to bleed, dealing 48% of your melee weapon's average damage over 6 sec."
				talents.Add(talent);
			}

			talents = talents.OrderBy(talent => talent.Tree * 1000 + talent.Row * 10 + talent.Column * 1).ToList();
			int index = 0;
			foreach (var talent in talents)
				talent.Index = index++;
			foreach (var talent in talents.Where(t => t.Prerequisite > 0))
				talent.Prerequisite = talents.First(t => t.ID == talent.Prerequisite.ToString()).Index;
			
			//Generaete the code
			string className = classData.Name + "Talents";
			StringBuilder code = new StringBuilder();
			code.AppendFormat(
@"	public partial class {0} : TalentsBase
	{{
		public override TalentsBase Clone()
		{{
			{0} clone = ({0})MemberwiseClone();
			clone._data = (int[])_data.Clone();
			clone._glyphData = (bool[])_glyphData.Clone();
			clone.TreeCounts = new int[] {{ -1, -1, -1 }};
			return clone;
		}}
", className);
			code.Append("\r\n");
            code.AppendFormat("		private int[] _data = new int[{0}];\r\n", talents.Count);
            code.Append("		public override int[] Data { get { return _data; } }\r\n");
            code.AppendFormat("		public {0}() {{ }}\r\n", className);
            code.AppendFormat("		public {0}(string talents, int[][] glyphEncodes=null)\r\n", className);
            code.Append("		{\r\n");
            code.Append("			LoadString(talents, glyphEncodes);\r\n");
            code.Append("		}\r\n");
            code.Append("		public static string[] TreeNames = new [] {");
			foreach (string tree in classData.TreeNames)
                code.AppendFormat("\r\n			@\"{0}\",", tree);
			code.Append("};\r\n\r\n");
            int lasttree = -1;
			foreach (TalentData talent in talents)
			{
                if (lasttree == -1) {
                    lasttree = 0;
                    code.Append(string.Format("		#region {0}\r\n", classData.TreeNames[lasttree]));
                    code.Append("		public override int TreeStartingIndexes_"+lasttree.ToString()+" { get { return "+talent.Index.ToString()+"; } }\r\n");
                } else if (lasttree == 0 && talent.Tree == 1) {
                    lasttree = 1;
                    code.Append("		#endregion\r\n");
                    code.Append(string.Format("		#region {0}\r\n", classData.TreeNames[lasttree]));
                    code.Append("		public override int TreeStartingIndexes_"+lasttree.ToString()+" { get { return "+talent.Index.ToString()+"; } }\r\n");
                } else if (lasttree == 1 && talent.Tree == 2) {
                    lasttree = 2;
                    code.Append("		#endregion\r\n");
                    code.Append(string.Format("		#region {0}\r\n", classData.TreeNames[lasttree]));
                    code.Append("		public override int TreeStartingIndexes_"+lasttree.ToString()+" { get { return "+talent.Index.ToString()+"; } }\r\n");
                }
                code.Append(GenerateComment(talent));
				code.AppendFormat("\r\n[TalentData(index: {0}, name: \"{1}\", maxPoints: {2}, icon: \"{3}\",\r\n tree: {4}, column: {5}, row: {6}, prerequisite: {7}, description: new [] {{",
					talent.Index, talent.Name, talent.MaxPoints, talent.Icon, talent.Tree, talent.Column, talent.Row, talent.Prerequisite == 0 ? -1 : talent.Prerequisite);
				foreach (string descRank in talent.Description)
				{
					//strip html breaks from descriptions
					string description = descRank.Replace("<br/>", "\r\n");
					code.AppendFormat("\r\n@\"{0}\",", description);
				}
				code.Append("})]\r\n");
				code.AppendFormat("public int {0} {{ get {{ return _data[{1}]; }} set {{ _data[{1}] = value; }} }}\r\n",
					PropertyFromName(talent.Name), talent.Index);
			}
            code.Append("		#endregion\r\n");
            code.Append("	}\r\n\r\n");

			textBoxCode.Text += code.ToString();
		}

		/// <summary>
		/// Generate a comment for the talent field, based on it's description( Replaceed changed value by [BaseNumber * Pts])
		/// </summary>
		/// <param name="Talent">Given talent</param>
		/// <returns>The comment</returns>
		private string GenerateComment(TalentData Talent)
		{
			string Comment = Talent.Description[Talent.Description.Length - 1];

			if (Talent.Description.Length > 1)
			{
				char[] SplitCharacter = new char[] { ' ', '%' };
				string[] FirstRank = Talent.Description[0].Split(SplitCharacter, StringSplitOptions.RemoveEmptyEntries);
				string[] LastRank = Talent.Description[Talent.Description.Length - 1].Split(SplitCharacter, StringSplitOptions.RemoveEmptyEntries);

				int ReplacePos = 0;

				//Description contains the same count of words for all ranks, diference only in some values
				for (int i = 0; i < Math.Min(FirstRank.Length, LastRank.Length); i++)
				{
					if (FirstRank[i] != LastRank[i])
					{
						//To avoid string like "... increase by 5."
						if (FirstRank[i].Contains(".") == false) FirstRank[i] = FirstRank[i] + ".0";
						else FirstRank[i] = FirstRank[i] + "0";

						float BaseNumber = 0;
						if (float.TryParse(FirstRank[i], NumberStyles.Any, new NumberFormatInfo(), out BaseNumber))
						{
							float MaxNumber = 0;
							string Replaced = "[{0} * Pts]";
							if (float.TryParse(LastRank[i], NumberStyles.Any, new NumberFormatInfo(), out MaxNumber))
							{
								int Base = Convert.ToInt32(BaseNumber);
								int Max = Convert.ToInt32(MaxNumber);

								//Number like: BaseNumber - 7, MaxNumber - 20
								if ((Base > 0) && ((Max / Base) * Base) != Max)
								{
									Replaced = "[" + MaxNumber + " / " + Talent.Description.Length + " * Pts]";
								}
							}

							Comment = Replace(Comment, LastRank[i], String.Format(Replaced, BaseNumber), ref ReplacePos);
						}
					}
				}
			}

			Comment = @"/// <summary>" + Environment.NewLine +
					  @"/// " + Comment.Replace("\r\n", "\r\n/// ") + Environment.NewLine +
					  @"/// </summary>";
			return Comment;
		}

		/// <summary>
		/// Returns a new string in which first occurrences of a specified string after position in this input string are replaced with another specified string.
		/// </summary>
		/// <param name="Text">Input string</param>
		/// <param name="OldValue">A string to be replaced</param>
		/// <param name="NewValue">A string to replace first occurrences of OldValue</param>
		/// <param name="Position">The starting character position, after wich, first occurences be replaced. 
		/// When this method returns, contains the 32-bit signed integer value, indicates the position, after replaced string  </param>
		/// <returns>A String equivalent to the input string but with first instance of OldValue replaced with NewValue</returns>
		private string Replace(string Text, string OldValue, string NewValue, ref int Position)
		{
			int NewPosition = Text.IndexOf(OldValue, Position);
			string Res = Text.Substring(0, NewPosition) + NewValue + Text.Substring(NewPosition + OldValue.Length);

			Position = NewPosition + NewValue.Length;
			return Res;
		}

		private string PropertyFromName(string name)
		{
			name = name.Replace("'", ""); // don't camel word after apostrophe
			string[] arr = name.Split(new char[] { ' ', ',', ':', '(', ')', '.', '-','!' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < arr.Length; i++)
			{
				arr[i] = Char.ToUpperInvariant(arr[i][0]) + arr[i].Substring(1);
			}
			return string.Join("", arr);
		}

		private class TalentData
		{
			public string ID { get; set; }
			public int Index { get; set; }
			public string Name { get; set; }
			public int MaxPoints { get; set; }
			public int Tree { get; set; }
			public int Column { get; set; }
			public int Row { get; set; }
			public string Icon { get; set; }
			public int Prerequisite { get; set; }
			public string[] Description { get; set; }
		}

		private class ClassData
		{
			public int ID { get; set; }
			public string Name { get; set; }
			public string[] TreeNames { get; set; }
			public string[] TreeIDs { get; set; }
		}
	}
}
